//
// SoapTrace.Net - A soap message monitoring utility. 
//
// Copyright (C) 2005 Matthew Ward
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Matthew Ward (mrward@users.sourceforge.net)

using SoapTrace.Core.Communication;
using System;
using System.Text;

namespace SoapTrace
{
	/// <summary>
	/// Managers soap message replay.
	/// </summary>
	public class SoapMessageReplayManager
	{
		/// <summary>
		/// Triggered when the soap request has finally been sent.
		/// </summary>
		public event SoapRequestMessageReceivedEventHandler SoapRequestMessageReceived;
		
		/// <summary>
		/// Triggered when a soap response has been received by the client.
		/// </summary>
		public event SoapResponseMessageReceivedEventHandler SoapResponseMessageReceived;
		
		/// <summary>
		/// The message replay client.
		/// </summary>
		private WebServiceClient m_ReplayClient;
		
		/// <summary>
		/// The name to be displayed for the source field of a replayed
		/// message.
		/// </summary>
		private static readonly string m_ReplayClientSource = "Replay"; 
		
		/// <summary>
		/// The replay client's local end point string.
		/// </summary>
		private string m_ReplayClientLocalEndPoint = String.Empty;
		
		
		
		/// <summary>
		/// Creates a new instance of the 
		/// <see cref="SoapMessageReplayManager"/>.
		/// </summary>
		public SoapMessageReplayManager( )
		{
		}
		
		/// <summary>
		/// Shuts down the replay manager.
		/// </summary>
		public void Shutdown( )
		{
			if( m_ReplayClient != null )
			{
				m_ReplayClient.Shutdown( );
			}
		}
		
		/// <summary>
		/// Replays the soap request to the specified remote server.
		/// </summary>
		/// <param name="header">The soap request header.</param>
		/// <param name="request">The soap request.</param>
		/// <param name="address">The IP address of the remote server.</param>
		/// <param name="port">The remote server's port.</param>
		public void Replay( string header, string request, string address, int port )
		{			
			string RequestText = GetReplayRequestText( header, request );
			
			ReplayUnmodified( RequestText, address, port );
		}
		
		/// <summary>
		/// Replays the soap request to the specified remote server.
		/// </summary>
		/// <param name="request">The soap request including the header.</param>
		/// <param name="address">The IP address of the remote server.</param>
		/// <param name="port">The remote server's port.</param>
		public void Replay( string request, string address, int port )
		{			
			string RequestText = GetReplayRequestText( request );
			
			ReplayUnmodified( RequestText, address, port );
		}
	
		/// <summary>
		/// Replays the soap request to the specified remote server.
		/// </summary>
		/// <param name="request">The original soap request.</param>
		/// <param name="address">The IP address of the remote server.</param>
		/// <param name="port">The remote server's port.</param>
		public void Replay( SoapRequest request, string address, int port )
		{			
			ReplayUnmodified( String.Concat( request.Header, request.Message ), address, port );
		}
		
		/// <summary>
		/// Replays the soap request to the specified remote server.
		/// </summary>
		/// <param name="request">The original soap request.</param>
		/// <param name="address">The IP address of the remote server.</param>
		/// <param name="port">The remote server's port.</param>
		public void ReplayUnmodified( string request, string address, int port )
		{
			CreateReplayClient( );
			
			if( !m_ReplayClient.IsConnected )
			{
				m_ReplayClient.Connect( address, port );
				
				lock( m_ReplayClientLocalEndPoint )
				{
					m_ReplayClientLocalEndPoint = m_ReplayClient.LocalEndPoint.ToString( );
				}
			}
			
			m_ReplayClient.Send( request );
		}		
		
		/// <summary>
		/// Checks whether the request or response is from the replay 
		/// client.
		/// </summary>
		/// <param name="source">The source of the message.</param>
		/// <returns><see langword="true"/> if the message is from 
		/// the replay client; otherwise <see langword="true"/>.
		/// </returns>
		public bool IsFromReplayClient( string source )
		{
			bool IsFromReplayClient = false;
			
			if( m_ReplayClient != null )
			{
				lock( m_ReplayClientLocalEndPoint )
				{
					if( m_ReplayClientLocalEndPoint == source )
					{
						IsFromReplayClient = true;
					}
				}
			}
			
			return IsFromReplayClient;
		}		
		
		/// <summary>
		/// Raises the SoapRequestMessageReceived event.
		/// </summary>
		/// <param name="source">The event source.</param>
		/// <param name="e">The event arguments.</param>
		protected void OnSoapRequestMessageReceived( object source, SoapRequestReceivedEventArgs e )
		{
			if( SoapRequestMessageReceived != null )
			{
				e.Request.Source = String.Concat( m_ReplayClientSource, " ", e.Request.Source );
			
				SoapRequestMessageReceived( this, e );
			}
		}
		
		/// <summary>
		/// Raises the SoapResponseMessageReceived event.
		/// </summary>
		/// <param name="source">The event source.</param>
		/// <param name="e">The event arguments.</param>
		protected void OnSoapResponseMessageReceived( object source, SoapResponseReceivedEventArgs e )
		{
			if( SoapResponseMessageReceived != null )
			{
				e.Response.Source = String.Concat( m_ReplayClientSource, " ", e.Response.Source );
			
				SoapResponseMessageReceived( this, e );
			}
		}			
		
		/// <summary>
		/// Creates the replay client and hooks up any events.
		/// </summary>
		private void CreateReplayClient( )
		{
			if( m_ReplayClient == null )
			{
				m_ReplayClient = new WebServiceClient( );
				
				m_ReplayClient.SoapRequestMessageSent += 
					new SoapRequestMessageReceivedEventHandler( OnSoapRequestMessageReceived );
				
				m_ReplayClient.SoapResponseMessageReceived += 
					new SoapResponseMessageReceivedEventHandler( OnSoapResponseMessageReceived );
			}
		}	
		
		/// <summary>
		/// Gets the request text to be replayed.
		/// </summary>
		/// <param name="fullRequest">The soap request including the http 
		/// header.</param>
		/// <returns>The full request text including the http 
		/// header.</returns>
		private string GetReplayRequestText( string fullRequest )
		{		
			// Parse header text.
				
			SoapRequestHeader RequestHeader = ParseHeader( fullRequest );
				
			string RequestBody = fullRequest.Substring( RequestHeader.Header.Length );

			return GetReplayRequestText( RequestHeader, RequestBody );
		}
		
		/// <summary>
		/// Gets the request text to be replayed.
		/// </summary>
		/// <param name="header">The soap request's http header.</param>
		/// <param name="requestBody">The soap request content.</param>
		/// <returns>The full request text including the http 
		/// header.</returns>
		private string GetReplayRequestText( string header, string requestBody )
		{
			SoapRequestHeader RequestHeader = ParseHeader( header );
				
			return GetReplayRequestText( RequestHeader, requestBody );
		}
		
		/// <summary>
		/// Gets the request text to be replayed.
		/// </summary>
		/// <param name="requestHeader">The soap request's http header.</param>
		/// <param name="requestBody">The soap request content.</param>
		/// <returns>The full request text including the http 
		/// header.</returns>
		private string GetReplayRequestText( SoapRequestHeader requestHeader, string requestBody )
		{
			SoapHeaderField ContentLengthField = requestHeader.Fields.Find( SoapHeader.ContentLengthFieldName );
			
			if( null == ContentLengthField )
			{
				// Add content length field.
				
				ContentLengthField = new SoapHeaderField( );
				ContentLengthField.Name = SoapHeader.ContentLengthFieldName;
				ContentLengthField.Value = requestBody.Length.ToString( );
				
				requestHeader.Fields.Add( ContentLengthField );
				requestHeader.Regenerate( );
			}
			else
			{
				// If header contains header length make sure it is 
				// correct.

				if( requestHeader.ContentLength == requestBody.Length )
				{
					// OK.
				}
				else
				{
					ContentLengthField.Value = requestBody.Length.ToString( );
					requestHeader.Regenerate( );
				}
			}
			
			return String.Concat( requestHeader.Header, requestBody );
		}		
		
		/// <summary>
		/// Parses the header text and creates a soap request header.
		/// </summary>
		/// <returns>A <see cref="SoapRequestHeader"/>.</returns>
		private SoapRequestHeader ParseHeader( string header )
		{
			SoapRequestHeader RequestHeader = new SoapRequestHeader( );
			
			bool FoundHeader = false;
			
			byte[ ] Bytes = UnicodeEncoding.UTF8.GetBytes( header );
			
			foreach( byte CurrentByte in Bytes )
			{
				FoundHeader = RequestHeader.AddByte( CurrentByte );
				
				if( FoundHeader )
				{
					break;
				}
			}
			
			if( FoundHeader == false )
			{
				throw new ApplicationException( "Could not parse header." );
			}
			
			return RequestHeader;
		}		
	}
}
